package com.course.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.context.UserContextHolder;
import com.common.domain.Pages.PageResult;
import com.common.exception.GlobalException;
import com.common.exception.course.CourseException;
import com.common.exception.users.UsersException;
import com.common.handler.RedisKeysHandler;
import com.common.rabbitmq.producer.core.UserProducer;
import com.common.tool.StringTool;
import com.course.domain.dto.CourseContentDto;
import com.course.domain.dto.CourseDto;
import com.course.domain.dto.SendComment;
import com.course.domain.params.CoursePageParams;
import com.course.domain.pojo.*;
import com.course.domain.vo.*;
import com.course.enums.CourseStatus;
import com.course.enums.CourseType;
import com.course.feign.MediaFeignClient;
import com.course.feign.OrderFeignClient;
import com.course.feign.UserFeignClient;
import com.course.mapper.CourseMapper;
import com.course.service.*;
import com.course.utils.RedisUtils;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.connection.RedisSetCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 小杰大人
 * @since 2025-02-18
 */
@Service
@RequiredArgsConstructor
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements ICourseService {
    private final MediaFeignClient mediaFeignClient;
    private final UserFeignClient userFeignClient;
    @Resource(name = "myTransactionTemplate")
    private final TransactionTemplate transactionTemplate;
    private final ICourseDetailService iCourseDetailService;
    private final RedisUtils redisUtils;
    private final StringRedisTemplate stringRedisTemplate;
    private final ICourseCommentService iCourseCommentService;
    private final ICourseCategoryService iCourseCategoryService;
    private final ICourseTagService iCourseTagService;
    private final OrderFeignClient orderFeignClient;
    private final UserProducer userProducer;


    @Override
    public PageResult<CourseVo> getCourseList(CoursePageParams pageParams) {
        if (Objects.isNull(pageParams.getPageOn())){
            pageParams.setPageOn(1);
        }
        if (Objects.isNull(pageParams.getPageSize())){
            pageParams.setPageSize(10);
        }
        Page<Course> page = new Page<>(pageParams.getPageOn(), pageParams.getPageSize());
        this.page(page, new LambdaQueryWrapper<Course>()
                .eq(pageParams.getCategoryId() != null, Course::getCategoryId, pageParams.getCategoryId())
                .like(!StrUtil.isEmptyIfStr(pageParams.getName()), Course::getName, pageParams.getName())
                .orderByDesc(Course::getCreateTime)    // 根据发布时间倒序排列
        );
        List<Course> records = page.getRecords();
        return new PageResult<>(page.getTotal(), this.CourseToVo(records));
    }

    @Override
    public FavoritePageVo favoritePageList(Long lastTimestamp, Long offset) {
        /**
         * 时间戳最大值：当前时间戳
         * 最小值：时间戳不可能为0
         */
        // 第一次查询：ZREVRANGEBYSCORE  key 当前时间戳 0 WITHSCORES LIMIT 首次为0 8 【每次返回八条数据】
        // 第N次查询：ZREVRANGEBYSCORE  key 上一次返回的最小时间戳 0 WITHSCORES LIMIT 最小时间戳出现的次数 8
        // 1: 结果集合
        Set<ZSetOperations.TypedTuple<String>> tupleResults;
        List<Long> courseIds = new ArrayList<>(8);  // 固定容量为8 每次只查8条返回
        Long usersId = UserContextHolder.getContext().getUsersId();
        // 2：第一次查询
        if (Objects.isNull(lastTimestamp)){
            tupleResults = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(
                    RedisKeysHandler.getUserFavoriteListKey(usersId),0,System.currentTimeMillis(),0,8);
        }else {
            // 3：第N次查询
            tupleResults = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(
                    RedisKeysHandler.getUserFavoriteListKey(usersId),0,lastTimestamp,offset,8);
        }
        if (tupleResults == null || tupleResults.isEmpty()){
            return null;
        }
        // 2.1: tupleResults包含了本次查询得到的用户收藏的课程id
        // 3: 计算下一次所需的 lastTimestamp 与 offset
        //  lastTimestamp = 本次查询的最小时间戳
        //  offset = 最小时间戳出现的次数
        long currentOffset = 0L;
        long currentLastTimestamp = 1L;
        for (ZSetOperations.TypedTuple<String> tuple : tupleResults) {
            // 3.1: 每一个分数值
            long timestamp = tuple.getScore().longValue();
            // 3.2: 收藏的每一个课程id
            String courseId = tuple.getValue();
            // 3.3: 计算出当前分数值出现的次数，由于返回的数据都是倒序排列 并且有序的
            if (currentLastTimestamp == timestamp){ // 出现相同的时间戳
                currentOffset++;
            }else {
                currentLastTimestamp = timestamp;   // 替换为更小的时间戳
                currentOffset = 1L; // 计数器归1
            }
            // 3.4：将课程id添加到集合中
            courseIds.add(Long.valueOf(courseId));
        }
        // 4: 构建返回值
        FavoritePageVo favoritePageVo = new FavoritePageVo();
        favoritePageVo.setOffset(currentOffset);
        favoritePageVo.setLastTimestamp(currentLastTimestamp);
        Long count = stringRedisTemplate.opsForZSet().count(RedisKeysHandler.getUserFavoriteListKey(usersId), 0, System.currentTimeMillis());
        favoritePageVo.setTotal(count);

        // 5: 根据得到的课程id集合，查询课程信息，构建CourseInfo集合
        if (!courseIds.isEmpty()){
            List<Course> courses = this.listByIds(courseIds);
            Set<Long> userIds = new HashSet<>();
            Set<Long> mediaIds = new HashSet<>();
            for (Course course : courses) {
                userIds.add(course.getUserid());
                mediaIds.add(course.getCoverId());
            }
            Map<Long, UserInfo> userInfoMap = userFeignClient.querySelectUserListByIds(new ArrayList<>(userIds)).getData();
            Map<Long, Media> mediaMap = mediaFeignClient.querySelectMediaByIds(new ArrayList<>(mediaIds)).getData();
            List<FavoritePageVo.CourseInfo> courseInfoList = courses.stream().filter(c -> !Objects.equals(c.getStatus(),3)).map(course -> {
                FavoritePageVo.CourseInfo courseInfo = new FavoritePageVo.CourseInfo();
                BeanUtils.copyProperties(course, courseInfo);
                courseInfo.setCourseId(course.getId());
                UserInfo userInfo = userInfoMap.get(course.getUserid());
                if (userInfo != null) {
                    courseInfo.setTeacherName(userInfo.getName());
                }
                Media media = mediaMap.get(course.getCoverId());
                if (media != null) {
                    courseInfo.setCourseImage(media.getUrl());
                }
                return courseInfo;
            }).toList();
            favoritePageVo.setInboxList(courseInfoList);
        }
        return favoritePageVo;
    }

    @Override
    public List<CourseVo> getCourseListPublishByUsername(String username) {
        Users users = userFeignClient.querySelectByUniqueIdentification(username).getData();
        if (users == null) {
            return null;
        }
        List<Course> courses = baseMapper.getCourseListPublishByUserId(users.getId());
        return this.CourseToVo(courses);
    }

    @Override
    public List<PurchasedCourseVo> getPurchasedCourses() {
        return getPurchasedCourses(UserContextHolder.getContext().getUsersId());
    }

    @Override
    public List<PurchasedCourseVo> getPurchasedCourses(Long userId) {
        List<Long> courseIds = orderFeignClient.getPurchasedCourseIds(userId).getData();
        if (courseIds == null || courseIds.isEmpty()){
            return Collections.emptyList();
        }
        List<Course> courses = baseMapper.getPurchasedCoursesByIds(courseIds);
        Set<Long> mediaIds = new HashSet<>();
        courses.forEach(k -> mediaIds.add(k.getCoverId()));
        Map<Long, Media> mediaMap = mediaFeignClient.querySelectMediaByIds(new ArrayList<>(mediaIds)).getData();
        return courses.stream()
                .map(course -> {
                    PurchasedCourseVo purchasedCourseVo = new PurchasedCourseVo();
                    purchasedCourseVo.setId(course.getId());
                    purchasedCourseVo.setName(course.getName());
                    // 课程封面
                    Media cover = mediaMap.get(course.getCoverId());
                    if (cover != null){
                        purchasedCourseVo.setMediaUrl(cover.getUrl());
                    }
                    // 课程标签
                    List<String> tagNamesByCourseId = iCourseTagService.getCourseTagNamesByIds(course.getTagIds());
                    purchasedCourseVo.setTags(tagNamesByCourseId);
                    return purchasedCourseVo;
                }).toList();
    }


    @Override
    public PageResult<CourseManagerVo> getCourseManagers(CoursePageParams coursePageParams) {
        // 登入用户的id
        Long usersId = UserContextHolder.getContext().getUsersId();
        Page<Course> res = this.page(
                new Page<>(coursePageParams.getPageOn(), coursePageParams.getPageSize()),
                new LambdaQueryWrapper<Course>()
                        .eq(Course::getUserid, usersId)
                        .like(!StrUtil.isEmptyIfStr(coursePageParams.getName()), Course::getName, coursePageParams.getName())
                        .eq(coursePageParams.getCategoryId() != null, Course::getCategoryId, coursePageParams.getCategoryId())
                        .orderByDesc(Course::getCreateTime)    // 根据发布时间倒序排列
        );
        Set<Long> mediaIds = new HashSet<>();
        res.getRecords().forEach(c -> mediaIds.add(c.getCoverId()));
        Map<Long, Media> mediaMap = mediaFeignClient.querySelectMediaByIds(new ArrayList<>(mediaIds)).getData();
        List<CourseManagerVo> courseManagerVos = res.getRecords().stream().map(
                courses -> {
                    CourseManagerVo courseManagerVo = new CourseManagerVo();
                    courseManagerVo.setName(courses.getName());
                    courseManagerVo.setId(courses.getId());
                    courseManagerVo.setStatus(courses.getStatus());
                    courseManagerVo.setType(courses.getType());
                    courseManagerVo.setCurPrice(courses.getCurPrice());
                    List<String> tagNames = iCourseTagService.getCourseTagNamesByIds(courses.getTagIds());
                    Optional.ofNullable(tagNames).ifPresent(t -> courseManagerVo.setTags(tagNames));
                    // 获取课程封面数据
                    Long mediaId = courses.getCoverId();
                    Media media = mediaMap.get(mediaId);
                    if (media != null) {
                        courseManagerVo.setCover(media.getUrl());
                    }
                    return courseManagerVo;
                }
        ).toList();
        return new PageResult<>(res.getTotal(), courseManagerVos);
    }

    @Override
    public CourseDetailVo getCourseDetails(Long courseId) {
        Assert.notNull(courseId, "课程id不能为空");
        return redisUtils.getPenetration(RedisKeysHandler.getCourseDetailsKey(courseId), CourseDetailVo.class,1,TimeUnit.HOURS,() -> {
            CourseDetailVo courseDetailVo = new CourseDetailVo();
            // 1: 补全课程基本信息
            Course course = this.getById(courseId);
            if (course == null){
                throw new CourseException(CourseException.COURSE_NULL);
            }
            BeanUtils.copyProperties(course,courseDetailVo);
            // 2：购买，点赞，收藏信息
            List<Object> list = stringRedisTemplate.executePipelined((RedisCallback<?>) connection -> {
                // 2.2: 获取课程点赞数量
                connection.setCommands().sCard(RedisKeysHandler.getCourseLikeListKey(courseId).getBytes(StandardCharsets.UTF_8));
                // 2.3: 获取课程评论数量
                connection.stringCommands().get(RedisKeysHandler.getCourseCommentMetaKey(courseId).getBytes(StandardCharsets.UTF_8));
                return null;
            });
            courseDetailVo.setLikesNum(list.get(0) == null ? 0 : (Long) list.get(0));
            courseDetailVo.setCommentNum(list.get(1) == null ? 0 : Long.parseLong(list.get(1).toString()));
            // 3: 课程教师信息
            Long teacherId = course.getUserid();
            UserInfo teacherUser = userFeignClient.querySelectUserInfoById(teacherId).getData();
            courseDetailVo.setTeacher(teacherUser);
            courseDetailVo.setTeacherFollowers(userFeignClient.getUserFansCount(teacherId).getData());
            // todo 获取当前用户总售出数量
            courseDetailVo.setTeacherSold(60L);
            int likes = this.list(new LambdaQueryWrapper<Course>().eq(Course::getUserid, teacherId)
                            .eq(Course::getStatus, CourseStatus.NORMAL.getStatus()))
                    .stream().mapToInt(Course::getLikes).sum();
            courseDetailVo.setTeacherLikes(likes);
            // 4: 课程大纲信息
            List<CourseDetail> courseDirectoryList = iCourseDetailService.getCourseDirectoryList(course.getId());
            List<CourseOutlineVo> courseOutlineVos = convertCourseDetailsToCourseDetailsDto(courseDirectoryList);
            courseDetailVo.setOutlines(courseOutlineVos);
            // 5: 常见问题
            List<CourseQuesVo> courseQuesVos = JSONUtil.toList(course.getQuestion(), CourseQuesVo.class);
            courseDetailVo.setQuestions(courseQuesVos);
            // 6: 媒资文件处理
            // 6.1：获取课程预览图片
            List<Long> longList = StringTool.convertIdsToList(course.getImageIds());
            if (longList != null && longList.size() > 0){
                Optional.ofNullable(mediaFeignClient.querySelectMediaByIds(longList).getData())
                        .ifPresent(k -> courseDetailVo.setImages(k.values().stream().map(v -> new CoursePreviewImageVo(v.getId(), v.getFileName(), v.getUrl())).toList()));
            }
            // 6.2：获取课程预览视频
            Optional.ofNullable(course.getVideoId()).ifPresent(id -> {
                Media videoMedia = mediaFeignClient.querySelectMediaById(id).getData();
                if (videoMedia != null){
                    courseDetailVo.setVideo(videoMedia.getUrl());
                }
            });
            // 7: 已售出数量
            courseDetailVo.setOrderNumber(course.getSales());
            // 8: 相关推荐
            List<CourseVo> relatedCourses = this.getRelatedCourses(courseId, 4);
            courseDetailVo.setRelatedCourses(relatedCourses);
            // 9：课程标签处理
            courseDetailVo.setTags(iCourseTagService.getCourseTagNamesByIds(course.getTagIds()));
            return courseDetailVo;
        });
    }

    private List<CourseOutlineVo> convertCourseDetailsToCourseDetailsDto(List<CourseDetail> courseDirectoryList) {
        Map<Long,CourseOutlineVo> courseOutlineVoMap = new HashMap<>();
        courseDirectoryList.stream()
                .filter(k -> {
                    if (k.getParentId() == null) {
                        courseOutlineVoMap.put(k.getId(), CourseOutlineVo.createCourseDetailsDto(k));
                        return false;
                    }
                    return true;
                }).forEach(k -> {
                    Long parentId = k.getParentId();
                    CourseOutlineVo parOutline = courseOutlineVoMap.get(parentId);
                    parOutline.getChildren().add(CourseOutlineVo.createCourseDetailsDto(k));
                });
        return new ArrayList<>(courseOutlineVoMap.values());
    }

    @Override
    public List<CourseVo> getRelatedCourses(Long courseId, Integer limit) {
        Assert.notNull(courseId, "课程id不能为空");
        if (limit == null) {
            limit = 4;  // 默认返回三条 过滤自己
        }
        // 获取当前课程的分类
        Long categoryId = this.getById(courseId).getCategoryId();
        // 获取该分类的下的剩余数据
        List<Course> list = this.list(Page.of(0, limit), new LambdaQueryWrapper<Course>()
                .eq(Course::getCategoryId, categoryId) // 查找相同分类的数据
                .ne(Course::getStatus, CourseStatus.OFF_SHELF.getStatus())   // 过滤下架课程
                .ne(Course::getId, courseId)   // 过滤自己
                .orderByAsc(Course::getCreateTime)    // 按创建时间排序
        );
        return CourseToVo(list);
    }
    private List<CourseVo> CourseToVo(List<Course> records) {
        if (records == null || records.isEmpty()){
            return Collections.emptyList();
        }
        // 教师id集合
        Set<Long> userIds = new HashSet<>();
        // 封面id集合
        Set<Long> mediaIds = new HashSet<>();
        for (Course course : records) {
            userIds.add(course.getUserid());
            mediaIds.add(course.getCoverId());
        }
        Map<Long, UserInfo> userInfoMap = userFeignClient.querySelectUserListByIds(new ArrayList<>(userIds)).getData();
        Map<Long, Media> mediaMap =  mediaFeignClient.querySelectMediaByIds(new ArrayList<>(mediaIds)).getData();

        return records.stream().map(item -> {
            CourseVo detailsVo = new CourseVo();
            BeanUtils.copyProperties(item, detailsVo);
            // 补全教师信息
            Long teacherId = item.getUserid();
            UserInfo userInfo = userInfoMap.get(teacherId);
            if (userInfo != null) {
                detailsVo.setTeacherName(userInfo.getName());
            }
            // 获取封面图片
            Long mediaId = item.getCoverId();
            Media media = mediaMap.get(mediaId);
            if (media != null) {
                detailsVo.setCoverUrl(media.getUrl());
            }
            // 销量
            detailsVo.setSales(item.getSales());
            // 标签
            List<String> tags = iCourseTagService.getCourseTagNamesByIds(item.getTagIds());
            if (tags != null){
                // 截取4个标签返回
                detailsVo.setTags(tags.subList(0,Math.min(4, tags.size())));
            }
            return detailsVo;
        }).collect(Collectors.toList());
    }

    @Override
    public Course getCourseInfoByUniqueIdentification(Object uniqueIdentification) {
        Assert.notNull(uniqueIdentification, "课程唯一标识符不能为空");
        Course course;
        if (NumberUtil.isNumber(uniqueIdentification.toString())){
            // 课程id
            course =  this.getById(Long.valueOf(uniqueIdentification.toString()));
        }else {
            // 课程名称
            course =  this.getOne(new LambdaQueryWrapper<Course>().eq(Course::getName, uniqueIdentification));
        }
        if (course == null){
            throw new IllegalArgumentException("课程唯一标识符类型错误");
        }
        return course;
    }



    @Override
    public List<TreeCourseDetailVo> getCourseDirectoryList(Long id) {
        return iCourseDetailService.getCourseContentList(id);
    }

    @Override
    public CourseContentDto getCourseContentById(Long courseId) {
        List<TreeCourseDetailVo> courseDirectoryList = iCourseDetailService.getCourseContentList(courseId);
        Course course = this.getById(courseId);
        CourseContentDto courseContentDto = new CourseContentDto();
        courseContentDto.setDirectory(courseDirectoryList);
        // 获取教师信息
        Long teacherId = course.getUserid();
        Users users = userFeignClient.querySelectByUniqueIdentification(teacherId).getData();
        if (users == null){
            throw new UsersException("教师信息异常");
        }
        courseContentDto.setTeacherName(users.getName());
        courseContentDto.setTeacherRemark(users.getRemark());
        courseContentDto.setTeacherId(users.getId());
        Long mediaId = users.getMediaId();  // 用户头像id
        if (mediaId != null){
            Media media = mediaFeignClient.querySelectMediaById(mediaId).getData();
            if (media != null){
                courseContentDto.setTeacherAvatar(media.getUrl());
            }
        }
        // 获取课程文章数量 与 课程视频数量
        int mediaVideoCount = 0;
        for (TreeCourseDetailVo treeCourseDetailsVo : courseDirectoryList) {
            // 编辑一级节点的二级节点
            mediaVideoCount += treeCourseDetailsVo.getChildren().size();

        }
        courseContentDto.setMediaVideoCount(mediaVideoCount);
        courseContentDto.setCreateTime(course.getCreateTime());
        courseContentDto.setName(course.getName());
        courseContentDto.setTags(iCourseTagService.getCourseTagNamesByIds(course.getTagIds()));
        return courseContentDto;
    }

    @Override
    public CourseCommentVo getCourseCommentList(Long courseId, Integer sorted) {
        return iCourseCommentService.getCourseCommentListByCourseId(courseId,sorted);
    }

    @Override
    public void sendComment(SendComment sendComment) {
        iCourseCommentService.sendComment(sendComment);
    }

    @Override
    public void likeComment(Long commentId) {
        iCourseCommentService.likeComment(commentId);
    }

    @Override
    public void publisher(CourseDto courseDto) {
        // 新增课程数据
        Course course = new Course();
        BeanUtils.copyProperties(courseDto, course);
        course.setStatus(1);
        course.setUserid(UserContextHolder.getContext().getUsersId());
        course.setDeleted((byte) 0);
        course.setVersion("1.0.0");
        if (Objects.equals(CourseType.CHARGE.getCode(), course.getType())) {
            course.setSales(0L);    // 课程销量
            course.setScore(BigDecimal.ZERO);   // 课程评分
        }
        refactorCourseByCourseDto(courseDto, course);
        transactionTemplate.executeWithoutResult(status -> {
            this.save(course);
            saveCourseDetailsList(courseDto, course.getId());
            // 推送给每一个粉丝的收件箱
            userProducer.send("user.follow.box", new FollowerInBox(course.getId(), course.getUserid()));
        });
    }

    @Override
    public void updateCourse(CourseDto courseDto) {
        Long courseId = courseDto.getId();
        Course course = this.getById(courseId);
        BeanUtils.copyProperties(courseDto, course);
        refactorCourseByCourseDto(courseDto, course);
        // 事务管理
        transactionTemplate.executeWithoutResult(status -> {
            this.updateById(course);
            // 删除课程内容数据 重建即可
            iCourseDetailService.remove(new LambdaQueryWrapper<CourseDetail>()
                    .eq(CourseDetail::getCourseId, courseId)
            );
            // 删除课程详情缓存
            redisUtils.del(RedisKeysHandler.getCourseDetailsKey(courseId));
            this.saveCourseDetailsList(courseDto, course.getId());
            // 推送给每一个粉丝的收件箱
            userProducer.send("user.follow.box", new FollowerInBox(course.getId(), course.getUserid()));
        });
    }

    /**
     * 处理课程 数据
     * @param courseDto
     * @param course
     */
    private static void refactorCourseByCourseDto(CourseDto courseDto, Course course) {
        // 1: 处理课程标签数据
        Optional.ofNullable(courseDto.getTags()).ifPresent(t -> {
            StringBuilder tagIds = new StringBuilder();
            t.stream().map(CourseTagVo::getId).forEach(v -> tagIds.append(v).append(","));
            tagIds.deleteCharAt(tagIds.length() - 1);
            course.setTagIds(tagIds.toString());
        });
        // 2: 处理媒资数据
        if (courseDto.getCover().getId() != null) {
            course.setCoverId(courseDto.getCover().getId());
        }
        if (courseDto.getVideo().getId() != null) {
            course.setVideoId(courseDto.getVideo().getId());
        }
        // 3: 处理课程预览图片
        Optional.ofNullable(courseDto.getPreviews()).ifPresent(previews -> {
            if (!previews.isEmpty()){
                StringBuilder ids = new StringBuilder();
                previews.stream().map(MediaVo::getId).forEach(v -> ids.append(v).append(","));
                ids.deleteCharAt(ids.length() - 1);
                course.setImageIds(ids.toString());
            }
        });
        // 4: 处理课程类型
        if (CourseType.FREE.getCode().equals(course.getType())){
            // 免费
            course.setOriPrice(BigDecimal.valueOf(0.00));
            course.setCurPrice(BigDecimal.valueOf(0.00));
            course.setDiscount(BigDecimal.valueOf(1.00));
        }
        // 5: 处理课程常见问题
        List<CourseQuesVo> questions = courseDto.getQuestions();
        // 5.1: 转为JSON字符串处理
        String jsonStr = JSONUtil.toJsonStr(questions);
        course.setQuestion(jsonStr);
    }

    @Override
    public CourseDto getCourseInfoById(Long courseId) {
        Assert.notNull(courseId, "课程id不能为空");
        // 1：获取课程信息
        Course course = this.getById(courseId);
        if (course == null) {
            throw new GlobalException("课程不存在");
        }
        CourseDto courseDto = new CourseDto();
        BeanUtils.copyProperties(course, courseDto);
        // 2：获取课程关联文件路径
        // 2.1：获取课程封面路径
        Long mediaCoverId = course.getCoverId();
        if (mediaCoverId != null) {
            Media media = mediaFeignClient.querySelectMediaById(mediaCoverId).getData();
            if (media != null) {
                courseDto.setCover(new MediaVo(media.getId(), media.getUrl()));
            }
        }
        // 2.2：获取课程宣传视频路径
        Long mediaPublishId = course.getVideoId();
        if (mediaPublishId != null) {
            Media media = mediaFeignClient.querySelectMediaById(mediaPublishId).getData();
            if (media != null) {
                courseDto.setVideo(new MediaVo(media.getId(), media.getUrl()));
            }
        }
        // 3：获取课程详细信息
        List<CourseDetail> courseDetails = iCourseDetailService.list(new LambdaQueryWrapper<CourseDetail>()
                .eq(CourseDetail::getCourseId, courseId)
                .orderByAsc(CourseDetail::getCreateTime)
        );
        List<CourseDto.CourseContentParent> courseContentList = toCourseContentList(courseDetails);
        courseDto.setCourseContent(courseContentList);
        // 4: 获取课程标签相关信息
        List<CourseTag> tags = iCourseTagService.getCourseTagsByCourseId(course.getTagIds());
        Optional.ofNullable(tags).ifPresent(v -> courseDto.setTags(v.stream().filter(k -> k.getParentId() != null).map(c -> new CourseTagVo(c.getParentId(),c.getId())).toList()));
        // 5: 获取课程预览图片
        List<Long> previewIds = StringTool.convertIdsToList(course.getImageIds());
        if (previewIds != null && !previewIds.isEmpty()){
            Map<Long, Media> mediaMap = mediaFeignClient.querySelectMediaByIds(previewIds).getData();
            List<MediaVo> previewList = mediaMap.values().stream().map(v -> new MediaVo(v.getId(), v.getUrl())).toList();
            courseDto.setPreviews(previewList);
        }
        // 6: 获取课程常见问题
        String question = course.getQuestion();
        List<CourseQuesVo> courseQuesVos = JSONUtil.toList(question, CourseQuesVo.class);
        courseDto.setQuestions(courseQuesVos);
        return courseDto;
    }


    public List<CourseDto.CourseContentParent> toCourseContentList(List<CourseDetail> courseDetailList) {
        Map<Long, CourseDto.CourseContentParent> map = new TreeMap<>();
        // 获取所有的父标题内容
        for (CourseDetail courseDetail : courseDetailList) {
            if (courseDetail.getParentId() == null) {
                map.put(courseDetail.getId(), new CourseDto.CourseContentParent(courseDetail.getTitle(), new ArrayList<>(), courseDetail.getIndexs()));
            }
        }
        // 处理子标题内容
        for (CourseDetail courseDetail : courseDetailList) {
            if (courseDetail.getParentId() == null){   // 过滤一级标题内容
                continue;
            }
            CourseDto.CourseContentChild courseContentChild = new CourseDto.CourseContentChild(courseDetail.getTitle(), null,null, null, courseDetail.getIndexs());
            Long mediaId = courseDetail.getMediaId();
            if (mediaId != null){
                Media media = mediaFeignClient.querySelectMediaById(mediaId).getData();
                Optional.ofNullable(media).ifPresent(m -> {
                    courseContentChild.setFileContent(m.getUrl());
                    courseContentChild.setFileId(courseDetail.getMediaId());
                    courseContentChild.setFileName(m.getFileName());
                });
            }
            map.get(courseDetail.getParentId()).getChildren().add(courseContentChild);
        }
        return new ArrayList<>(map.values());
    }

    @Override
    public void updateStatus(Course course) {
        Assert.notNull(course.getId(), "课程id不能为空");
        Assert.notNull(course.getStatus(), "修改状态不能为空");
        Course targetCourse = new Course();
        targetCourse.setId(course.getId());
        targetCourse.setStatus(course.getStatus());
        transactionTemplate.executeWithoutResult(s -> {
            updateById(targetCourse);  // 操作数据库
            redisUtils.del(RedisKeysHandler.getCourseDetailsKey(course.getId()));    // 删除课程详情缓存
        });
    }

    @Override
    @Transactional
    public void removeCourse(Long courseId) {
        baseMapper.deletedCourse(courseId);
    }

    @Override
    @Transactional
    public void removeCourseByUserId(Long userId) {
        // 获取当前用户发布的课程集合
        List<Course> list = this.list(new LambdaQueryWrapper<Course>()
                .eq(Course::getUserid, userId));
        for (Course course : list) {
            this.removeCourse(course.getId());
        }
    }

    @Override
    @Transactional
    public void likeCourse(Long courseId) {
        // 1：获取当前用户id
        Long usersId = UserContextHolder.getContext().getUsersId();
        // 2：判断当前用户是否点赞了该课程
        Boolean member = stringRedisTemplate.opsForSet().isMember(RedisKeysHandler.getCourseLikeListKey(courseId), usersId.toString());
        if (BooleanUtil.isTrue(member)) {
            // 3：已点赞
            // 3.1：数据库中点赞数-1  先操作数据库 后操作缓存 并且确保数据库操作成功后在修改缓存
            boolean updated = this.update().setSql("likes = likes - 1").eq("id", courseId).update();
            if (updated) {
                // 3.4：批处理一次执行
                RedisCallback<Object> redisCallback = connection -> {
                    RedisSetCommands redisSetCommands = connection.setCommands();   // 操作Set集合
                    // 3.2：Redis中取消 课程点赞列表记录
                    redisSetCommands.sRem(RedisKeysHandler.getCourseLikeListKey(courseId).getBytes(), usersId.toString().getBytes());
                    // 3.3：Redis中取消 用户点赞列表记录
                    redisSetCommands.sRem(RedisKeysHandler.getUserLikeListKey(usersId).getBytes(), courseId.toString().getBytes());
                    return null;
                };
                stringRedisTemplate.executePipelined(redisCallback);
            }
        } else {
            // 4：未点赞
            // 4.1：数据库中点赞数+1
            boolean updated = this.update().setSql("likes = likes + 1").eq("id", courseId).update();
            if (updated) {
                // 4.4：批处理一次执行
                RedisCallback<Object> redisCallback = connection -> {
                    RedisSetCommands redisSetCommands = connection.setCommands();   // 操作Set集合
                    // 4.2：Redis中添加 课程点赞列表记录
                    redisSetCommands.sAdd(RedisKeysHandler.getCourseLikeListKey(courseId).getBytes(), usersId.toString().getBytes());
                    // 4.3：Redis中添加 用户点赞列表记录
                    redisSetCommands.sAdd(RedisKeysHandler.getUserLikeListKey(usersId).getBytes(), courseId.toString().getBytes());
                    return null;
                };
                stringRedisTemplate.executePipelined(redisCallback);
            }
        }
    }

    @Override
    public void favoriteCourse(Long courseId) {
        // 1：获取当前用户id
        Long usersId = UserContextHolder.getContext().getUsersId();
        // 2：判断当前用户是否收藏了该课程
        Boolean member = stringRedisTemplate.opsForSet().isMember(RedisKeysHandler.getCourseFavoriteListKey(courseId), usersId.toString());
        if (BooleanUtil.isTrue(member)) {
            // 3：已收藏
            // 3.1：数据库中收藏数-1  先操作数据库 后操作缓存 并且确保数据库操作成功后在修改缓存
            boolean updated = this.update().setSql("favorite = favorite - 1").eq("id", courseId).update();
            if (updated) {
                // 3.4：批处理一次执行
                RedisCallback<Object> redisCallback = connection -> {
                    // 3.2：Redis中取消 课程收藏列表记录 Set集合
                    connection.setCommands().sRem(RedisKeysHandler.getCourseFavoriteListKey(courseId).getBytes(), usersId.toString().getBytes());
                    // 3.3：Redis中取消 用户收藏列表记录 ZSet集合 用于后期滚动分页
                    connection.zSetCommands().zRem(RedisKeysHandler.getUserFavoriteListKey(usersId).getBytes(), courseId.toString().getBytes());
                    return null;
                };
                stringRedisTemplate.executePipelined(redisCallback);
            }
        } else {
            // 4：未点赞
            // 4.1：数据库中收藏数+1
            boolean updated = this.update().setSql("favorite = favorite + 1").eq("id", courseId).update();
            if (updated) {
                // 4.4：批处理一次执行
                RedisCallback<Object> redisCallback = connection -> {
                    // 4.2：Redis中添加 课程收藏列表记录
                    connection.setCommands().sAdd(RedisKeysHandler.getCourseFavoriteListKey(courseId).getBytes(), usersId.toString().getBytes());
                    // 4.3：Redis中添加 用户收藏列表记录 ZSet集合 用于后期滚动分页
                    connection.zSetCommands().zAdd(RedisKeysHandler.getUserFavoriteListKey(usersId).getBytes(), System.currentTimeMillis() ,courseId.toString().getBytes());
                    return null;
                };
                stringRedisTemplate.executePipelined(redisCallback);
            }
        }
    }


    @Override
    public InboxPageVo inboxPage(Long lastTimestamp, Integer offset) {
        // 1：基于Redis的SortedSet 数据结构 实现滚动分页查询 ZREVRANGEBYSCORE key max min WITHSCORES limit 0 8
        Set<ZSetOperations.TypedTuple<String>> tuples;
        if (offset == null){
            // 1.1：第一次查询 lastTimestamp为当前时间戳【当前时间戳最大】 offset为0
            tuples = stringRedisTemplate.opsForZSet()
                    .reverseRangeByScoreWithScores(RedisKeysHandler.getUserInboxKey(UserContextHolder.getContext().getUsersId()),
                            0, System.currentTimeMillis(), 0, 8);
        }else {
            // 1.2：第N次查询 lastTimestamp为上一次查询的最后一条时间戳 offset为该时间戳出现的次数
            tuples = stringRedisTemplate.opsForZSet()
                    .reverseRangeByScoreWithScores(RedisKeysHandler.getUserInboxKey(UserContextHolder.getContext().getUsersId()),
                            0, lastTimestamp, offset, 8);
        }
        if (tuples == null || tuples.isEmpty()){
            return null;
        }
        // 2：获取下一次查询需要的最后一条数据max 以及 对应的偏移量offset
        int os = 1; // 默认偏移量为1 因为最后一条元素至少有一个
        long min = 0;    // 最小分数值 时间戳不可能为0
        List<Long> ids = new ArrayList<>(tuples.size());    // 存储本次查询元素的id集合
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            // 2.1：得到每一个元素的分数
            long score = tuple.getScore().longValue();
            // 2.2：得到每一个元素的值
            String courseId = tuple.getValue();
            // 如果当前是最小值则出现次数 + 1
            if (min == score){
                os++;
            }else {
                // 否则更新最小值 重置出现次数
                min = score;
                os = 1;
            }
            ids.add(Long.valueOf(courseId));
        }
        // 3：根据元素id集合得到具体的课程消息数据
        List<InboxPageVo.CourseMessage> courseMessageList = baseMapper.listIdsSort(ids).stream().map(courses1 -> {
            InboxPageVo.CourseMessage courseMessage = new InboxPageVo.CourseMessage();
            courseMessage.setCourseId(courses1.getId());
            courseMessage.setContent(courses1.getDescription());
            Long userId = courses1.getUserid();   // 用户id
            Users users = userFeignClient.querySelectByUniqueIdentification(userId).getData();
            Long mediaId = users.getMediaId();  // 用户头像
            if (mediaId != null){
                Media media = mediaFeignClient.querySelectMediaById(mediaId).getData();
                Optional.ofNullable(media).ifPresent(media1 -> courseMessage.setAvatar(media1.getUrl()));
            }
            courseMessage.setTitle(users.getName() + " 更新了课程-" + courses1.getName());
            courseMessage.setCreateTime(courses1.getCreateTime());
            return courseMessage;
        }).toList();
        // 4: 构建返回信息
        InboxPageVo inboxPageVo = new InboxPageVo();
        inboxPageVo.setInboxList(courseMessageList);
        inboxPageVo.setOffset(os);
        inboxPageVo.setLastTimestamp(min);
        // 4.1：获取消息总数
        Long count = stringRedisTemplate.opsForZSet().count(RedisKeysHandler.getUserInboxKey(UserContextHolder.getContext().getUsersId()), 0, System.currentTimeMillis());
        inboxPageVo.setTotal(count);
        // 5：返回
        return inboxPageVo;
    }

    @Override
    public void clearInbox() {
        stringRedisTemplate.delete(RedisKeysHandler.getUserInboxKey(UserContextHolder.getContext().getUsersId()));
    }

    @Override
    public CourseCurInfoVo getCourseCurInfo() {
        CourseCurInfoVo courseCurInfoVo = new CourseCurInfoVo();
        // 1: 得到当前操作用户的id
        Long usersId = UserContextHolder.getContext().getUsersId();
        // 2: 获取当前用户购买的课程数量
        courseCurInfoVo.setCourseNumber(orderFeignClient.getPurchasedCourseIds(usersId).getData().size());
        // 3: 获取当前用户点赞的课程数量
        Set<String> members1 = stringRedisTemplate.opsForSet().members(RedisKeysHandler.getUserLikeListKey(usersId));
        if (members1 == null || members1.isEmpty()){
            courseCurInfoVo.setLikeNumber(0);
        }else {
            courseCurInfoVo.setLikeNumber(members1.size());
        }
        // 4：获取当前用户关注的用户数量
        Set<String> members2 = stringRedisTemplate.opsForZSet().range(RedisKeysHandler.getUserFollowListKey(usersId),0, -1);
        if (members2 == null || members2.isEmpty()){
            courseCurInfoVo.setFollowNumber(0);
        }else {
            courseCurInfoVo.setFollowNumber(members2.size());
        }
        return courseCurInfoVo;
    }

    @Override
    public List<TreeCourseTagsVo> getCourseTags() {
        return iCourseTagService.getCourseTagsAsTree();
    }

    @Override
    public CourseLikeAndFavoriteVo isLikeAndFavorite(Long usersId, Long courseId) {
        CourseLikeAndFavoriteVo courseLikeAndFavoriteVo = new CourseLikeAndFavoriteVo();
        List<Object> list = stringRedisTemplate.executePipelined((RedisCallback<?>) connection -> {
            // 1: 判断是否点赞 查询redis 当前课程下点赞列表是否含有当前学生
            connection.setCommands().sIsMember(RedisKeysHandler.getCourseLikeListKey(courseId).getBytes(StandardCharsets.UTF_8),
                    usersId.toString().getBytes(StandardCharsets.UTF_8));
            // 2: 判断是否收藏 查询redis 当前课程下收藏列表是否含有当前学生
            connection.setCommands().sIsMember(RedisKeysHandler.getCourseFavoriteListKey(courseId).getBytes(StandardCharsets.UTF_8),
                    usersId.toString().getBytes(StandardCharsets.UTF_8));
            return null;
        });
        courseLikeAndFavoriteVo.setLike(list.get(0) != null && (Boolean) list.get(0));
        courseLikeAndFavoriteVo.setFavorite(list.get(1) != null && (Boolean) list.get(1));
        return courseLikeAndFavoriteVo;
    }

    @Override
    public CourseBuyVo getBuyCourseById(Long courseId) {
        CourseBuyVo courseBuyVo = new CourseBuyVo();
        // 1: 补全课程基本信息
        Course course = this.getById(courseId);
        if (course == null){
            throw new CourseException(CourseException.COURSE_NULL);
        }
        BeanUtils.copyProperties(course,courseBuyVo);
        // 2: 课程教师信息
        Long teacherId = course.getUserid();
        UserInfo teacherUser = userFeignClient.querySelectUserInfoById(teacherId).getData();
        courseBuyVo.setUserName(teacherUser.getName());
        courseBuyVo.setUserDegree(teacherUser.getDegree());
        courseBuyVo.setUserId(teacherId);
        // 3: 获取封面图片
        Long mediaId = course.getCoverId();
        Optional.ofNullable(mediaFeignClient.querySelectMediaById(mediaId).getData())
                .ifPresent(media -> courseBuyVo.setCover(media.getUrl()));
        // 4：课程标签处理
        courseBuyVo.setTags(iCourseTagService.getCourseTagNamesByIds(course.getTagIds()));
        // 5: 课程分类名称
        courseBuyVo.setCategoryName(iCourseCategoryService.getById(course.getCategoryId()).getName());
        return courseBuyVo;
    }

    @Override
    @Transactional
    public void saleIncrement(Long courseId) {
        this.update().setSql("sales = sales + 1").eq("id", courseId).update();
    }

    private void saveCourseDetailsList(CourseDto courseDto, Long courseId) {
        // 新增课程详细数据
        List<CourseDto.CourseContentParent> courseContent = courseDto.getCourseContent();
        List<CourseDetail> courseDetailList = new ArrayList<>();
        try {
            IdentifierGenerator identifierGenerator = new DefaultIdentifierGenerator(InetAddress.getLocalHost());
            for (int i = 0; i < courseContent.size(); i++) {
                CourseDto.CourseContentParent parent = courseContent.get(i);
                CourseDetail parentCourseDetail = new CourseDetail();
                parentCourseDetail.setTitle(parent.getTitle());
                parentCourseDetail.setCourseId(courseId);
                long parentId = identifierGenerator.nextId(new Object()).longValue();
                parentCourseDetail.setId(parentId);
                parentCourseDetail.setIndexs(String.valueOf(i + 1));
                List<CourseDto.CourseContentChild> children = parent.getChildren();
                for (int j = 0; j < children.size(); j++) {
                    CourseDto.CourseContentChild child = children.get(j);
                    CourseDetail childrenCourseDetail = new CourseDetail();
                    childrenCourseDetail.setCourseId(courseId);
                    childrenCourseDetail.setTitle(child.getTitle());
                    childrenCourseDetail.setMediaId(child.getFileId());
                    childrenCourseDetail.setParentId(parentCourseDetail.getId());
                    childrenCourseDetail.setIndexs(parentCourseDetail.getIndexs() + "-" + (j + 1));
                    courseDetailList.add(childrenCourseDetail);
                }
                courseDetailList.add(parentCourseDetail);
            }
            transactionTemplate.execute(status -> iCourseDetailService.saveBatch(courseDetailList));
        } catch (Exception e) {
            e.printStackTrace();
            throw new GlobalException("课程详细数据保存失败");
        }
    }
}
